﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using XMLGener.Utils;

namespace XMLGener.Data
{
    public delegate void NonParametricMethod();

    /// <summary>
    /// This class represents node in XML structure of 
    /// XTemlpate. 
    /// </summary>
    public class XMLGenerNode
    {
        
        public event NonParametricMethod nodeChanged;

        public event NonParametricMethod originalChanged;

        /// <summary>
        /// Default constructor. Name is empty.
        /// </summary>
        public XMLGenerNode()
        {
            defaultInitialization();
        }

        /// <summary>
        /// 
        /// </summary>
        public static decimal MAX_ELEMENTS_COUNT = 10000000;

        /// <summary>
        /// Create new node with name
        /// </summary>
        /// <param name="name">name of new node</param>
        public XMLGenerNode(String name)
        {
            defaultInitialization();
            this.name = name;
        }

        /// <summary>
        /// 
        /// </summary>
        public enum XNodeType {
            /// <summary>
            /// Document type of XGenerNode
            /// </summary>
            DOCUMENT,
            /// <summary>
            /// Text node type of XGenerNode
            /// </summary>
            TEXTNODE,
            /// <summary>
            /// Elements type of XGenerNode
            /// </summary>
            ELEMENT,
            /// <summary>
            /// Elements type of XGenerNode
            /// </summary>
            ELEMENTS,
            /// <summary>
            /// Attribute type of XGenerNode
            /// </summary>
            ATTRIBUTE,
            /// <summary>
            /// Node for element types definition
            /// </summary>
            ELEMENTTYPES,
            /// <summary>
            /// Node for data types definition
            /// </summary>
            DATATYPES,
            /// <summary>
            /// Node for data type definition
            /// </summary>
            DATATYPE,
            /// <summary>
            /// Undefined XGenerNode type
            /// </summary>
            NONE};

        /// <summary>
        /// Default node type.
        /// </summary>
        public XNodeType xtype = XNodeType.NONE;

        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XTEXTNODE_STR = "textnode";
        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XELEMENT_STR = "element";

        /// <summary>
        /// Text used for parsing document from/to text file for main element.
        /// </summary>
        public const String XMAINELEMENT_STR = "mainelement";

        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XELEMENTS_STR = "elements";
        
        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XATTRIBUTE_STR = "attribute";
        
        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XATTRIBUTES_STR = "attributes";

        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XTYPES_STR = "types";

        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XDATATYPES_STR = "datatypes";

        /// <summary>
        /// Text used for parsing document from/to text file.
        /// </summary>
        public const String XDATATYPE_STR = "datatype";

        /// <summary>
        /// Available values are "element","elements","attribute" and "mainelement".It depends on
        /// node type type.
        /// </summary>
        protected String nodeTypeStr = "";

        /// <summary>
        /// Document node, which contains this node
        /// </summary>
        private XMLGenerDocument _ownderDocument = null;


        /// <summary>
        /// Document node, which contains this node
        /// </summary>
        public XMLGenerDocument ownerDocument
        {
            get
            {
                //if reference to owner document is null, try to get 
                // reference from parent node.
                if (this._ownderDocument == null && this.parentNode!=null)
                { 
                    this._ownderDocument = this.parentNode.ownerDocument;
                }
                return this._ownderDocument;
            }
            set 
            {
                this._ownderDocument = value;
            }
        }
        
        /// <summary>
        /// Logger class used as output for log messages
        /// </summary>
        protected XLogger _logger = null;

        /// <summary>
        /// Logger
        /// </summary>
        public  virtual XLogger logger
        {
            get 
            {
                if (this._logger == null && this.ownerDocument != null)
                    this._logger = this.ownerDocument.logger;
                else
                    this._logger = XLogger.instance;
                return this._logger;
            }
        }



        /// <summary>
        /// Create new node as child of node "parent" and 
        /// also create new xml element in document as child
        /// of parents xml element. This constructor is used 
        /// at user-editing.
        /// </summary>
        /// <param name="name">name of this node</param>
        /// <param name="parent">parent node</param>
        public XMLGenerNode(String name, XMLGenerNode parent)
        {
            
            defaultInitialization();
            this.parentNode = parent;
            if (parentNode != null)
                this.parentNode.addChildNode(this);
            XmlElement parentXml = parent.xmlElem;
            if (parentXml == null)
            {
                logger.addErrorLog(this.GetType().ToString(), "XMLGenerNode \"" + parentNode.name + "\" has not assigned xml node.");
                return;
            }
            this.xmlElem = parentXml.OwnerDocument.CreateElement(nodeTypeStr);
            this.name = name;
            // In xml document element "attributes" must be last element
            // so new node must be created before this element
            XmlElement attributesElement = XMLUtils.getSubElement(parentXml, XATTRIBUTES_STR);
            if (attributesElement == null)
                parentXml.AppendChild(xmlElem);
            else
                parentXml.InsertBefore(xmlElem, attributesElement);

        }

        /// <summary>
        /// Consturctor used in loading method.
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="xmlElem"></param>
        public XMLGenerNode(XMLGenerNode parent, XmlElement xmlElem)
        {
            defaultInitialization();
            this.parentNode = parent;
            if (parentNode != null)
                this.parentNode.addChildNode(this);
            this.xmlElem = xmlElem;
        }

        /// <summary>
        /// Default class initialization
        /// </summary>
        protected virtual void defaultInitialization()
        {
            this.childNodes = new List<XMLGenerNode>();
        }

        
        /// <summary>
        /// Add node to childNodes and set self as parent node.
        /// </summary>
        /// <param name="node">Node to add to childNodes</param>
        public virtual void addChildNode(XMLGenerNode node)
        {
            if (this.childNodes.Contains(node))
                return;
            this.childNodes.Add(node);
            if (node.parentNode != this)
                node.parentNode = this;
        }

       

        /// <summary>
        /// List all child nodes
        /// </summary>
        public virtual List<XMLGenerNode> allChildNodes
        {
            get { return childNodes;}
        }
        /// <summary>
        /// List of child nodes
        /// </summary>
        internal List<XMLGenerNode> childNodes = null;
        
        /// <summary>
        /// Parent node.
        /// </summary>
        public XMLGenerNode parentNode = null;

        /// <summary>
        /// 
        /// </summary>
        public XmlElement xmlElem = null;

        /// <summary>
        /// Indication, if this node is inherited.
        /// </summary>
        protected bool _isCopy = false;

      
        /// <summary>
        ///  Return indictor, if this node was inherited from other node
        /// </summary>
        public bool isCopy
        {
            get { return _isCopy; }
        }

        protected XMLGenerNode originalNode = null;


        public virtual XMLGenerNode getCopy()
        {
            return this.getCopy(1);
        }
        public virtual XMLGenerNode getCopy(int depth)
        {

            XMLGenerNode result = new XMLGenerNode(null, this.xmlElem);
            result.xmlElem = this.xmlElem;
            result.originalNode = this;
            result._isCopy = true;
            if (depth > 1)
            {
                int newDepth = depth - 1;
                foreach (XMLGenerNode child in this.childNodes)
                    result.addChildNode(child.getCopy(newDepth));
            }

            return result;
        }


        /// <summary>
        /// Invalid indication. It can be set internally.
        /// </summary>
        protected bool _isInvalid = false;
        
        /// <summary>
        /// Indication, if this node is invalid. 
        /// It will be ignored during generating process.
        /// </summary>
        public bool isInvalid
        { 
            get { return _isInvalid; }
        }

        /// <summary>
        /// Return avialable types for this type of node
        /// </summary>
        /// <returns>list of strings</returns>
        public virtual XDataTypeCollection listAvailabeTypes()
        {
            return null;
        }
        
        /// <summary>
        /// Return avialable types for this type of node
        /// </summary>
        /// <returns>list of strings</returns>
        public virtual XDataTypeCollection listAvailabeParents()
        {
            return null;
        }

        /// <summary>
        /// Function returns content of attribute from xml source file
        /// </summary>
        /// <param name="name">attribute name</param>
        /// <returns>attribute's content</returns>
        protected string getXMLParam(string name)
        {
            return getXMLParam(name, null);
        }

        /// <summary>
        /// Function returns content of attribute from xml source file
        /// </summary>
        /// <param name="name">attribute name</param>
        /// <param name="defValue">default value </param>
        /// <returns>attribute's content</returns>
        protected string getXMLParam(string name,string defValue)
        {
            return XMLUtils.getAttribute(this.xmlElem, name, defValue);
        }

        /// <summary>
        /// Function sets attribute's content.
        /// </summary>
        /// <param name="name">attribute's name</param>
        /// <param name="value">conent</param>
        protected void setXMLParam(string name, string value)
        {

            XMLUtils.setAttribute(this.xmlElem, name, value, logger);

        }

        /// <summary>
        /// Function returns integer content of attribute from xml source file
        /// </summary>
        /// <param name="name">attrubute's name</param>
        /// <param name="min">nimimal boundary</param>
        /// <param name="max">maximal boundary</param>
        /// <returns></returns>
        protected int getIntXMLParam(string name, int min, int max)
        {
            string val = getXMLParam(name);
            int result = Convert.ToInt32(val);
            if (result < min) return min;
            if (result > max) return max;
            return result ;
        }

        /// <summary>
        /// Function sets attribute's interger value.
        /// </summary>
        /// <param name="name">attribute's name</param>
        /// <param name="value">vale</param>
        protected void setIntXMLParam(string name, int value)
        {
            setXMLParam(name, Convert.ToString(value));
        }

        /// <summary>
        /// Function returns attribute's boolean content 
        /// </summary>
        /// <param name="name">attribute's name</param>
        /// <param name="defValue">default value</param>
        /// <returns></returns>
        protected bool getBoolParam(string name, bool defValue)
        {
            string val = getXMLParam(name);
            if (string.IsNullOrEmpty(val))
                return defValue;
            if (val == "1")
                return true;
            return false;

        }

        /// <summary>
        /// Function sets attribute's boolean value into xml
        /// </summary>
        /// <param name="name">attribute's name</param>
        /// <param name="value">value for set</param>
        protected  virtual void setBoolParam(string name, bool value)
        {
            setXMLParam(name, (value) ? "1" : "0");
        }


        /// <summary>
        /// Function returns decimal content of attribute from xml source file
        /// </summary>
        /// <param name="name">attribute's name</param>
        /// <param name="min">minimal boundary</param>
        /// <param name="max">maximal boundary</param>
        /// <returns></returns>
        protected decimal getDecimalXMLParam(string name,decimal min, decimal max)
        {
            string val = getXMLParam(name);
            decimal result = Convert.ToDecimal(val);
            if (result < min) return min;
            if (result > max) return max;
            return result ;
        }

        /// <summary>
        /// Function sets attribute's decimal content.
        /// </summary>
        /// <param name="name">attribute's name</param>
        /// <param name="value">conent</param>
        protected virtual void setDecimalXMLParam(string name, decimal value)
        {
            setXMLParam(name, Convert.ToString(value));
        }




        /// <summary>
        /// Delete current node from tree structure.
        /// </summary>
        public void delete()
        {
            if (this.parentNode == null)
                return;
            this.parentNode.removeChildNode(this);
        }

        /// <summary>
        /// Delete child node from child nodes.
        /// </summary>
        /// <param name="child">child node</param>
        protected virtual void removeChildNode(XMLGenerNode child)
        {
            if (this.childNodes.IndexOf(child) < 0)
                return;
            this.childNodes.Remove(child);
            this.xmlElem.RemoveChild(child.xmlElem);
        }

        /// <summary>
        /// Move this node down (=count&gt;0) or down (count&lt;0).
        /// </summary>
        /// <param name="count">count of elemnents to skip over</param>
        public bool changePosition(int count)
        {
            if (count != 1 && count != -1 )
                return false;
            if (this.parentNode == null)
                return false;
            return this.parentNode.changeChildPosition(this, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="child">child node</param>
        /// <param name="count">direction and distance to move ( -1 or 1)</param>
        /// <returns>True if node's position was changed</returns>
        protected virtual bool changeChildPosition(XMLGenerNode child, int count)
        {
            int pos = this.childNodes.IndexOf(child);
            // check position boundaries
            int i = pos;
            if (i < 0) return false;
            i += count;
            if (i < 0) i = 0;
            if (i >= this.childNodes.Count) i = this.childNodes.Count - 1;
 
            
            // try to swap position of XML node
            if (count > 0)
            {
                // move element down
                XmlElement nextElem = XMLUtils.getNextElement(child.xmlElem);
                if (nextElem == null || nextElem.Name == XATTRIBUTES_STR)
                {
                    // there is no option to move
                    return false;
                }
                if (child.xmlElem == nextElem)
                {
                    return false;
                }
                this.xmlElem.RemoveChild(child.xmlElem);
                this.xmlElem.InsertAfter(child.xmlElem,nextElem);
            }
            else 
            { 
                // move element up
                XmlElement prevElem = XMLUtils.getPrevoiusElement(child.xmlElem);
                if (prevElem == null)
                {
                    // there is no option to move
                    return false;
                }
                this.xmlElem.RemoveChild(child.xmlElem);
                this.xmlElem.InsertBefore(child.xmlElem,prevElem);
            }

            // set position of XGenerNode
            this.childNodes.RemoveAt(pos);
            this.childNodes.Insert(i, child);

            return true;
        }

        

        //---------------------------
        // attributes from xml
        //---------------------------
        /// <summary>
        /// Function will change value into valid name
        /// of xml element or attribute.
        /// </summary>
        /// <param name="value">name of element or attribute for validation</param>
        /// <returns>valid string</returns>
        protected virtual string validateName(string value)
        {
            if (value == null)
                return null;
            // remove all white space
            string trim = Regex.Replace(value, @"[^a-zA-Z0-9]", "");
            // remove all numbers at the begining
            string noStartNumber = Regex.Replace(trim, @"^[0-9]*", "");

            return noStartNumber;
        }

        private bool _nameNotChanged = false;
        private string _name = null; 
        /// <summary>
        /// Return name of node (xml element name in generated result)
        /// </summary>
        public virtual String name
        {
            get {
                if (_nameNotChanged) return _name;
                _nameNotChanged=true;
                return _name=validateName(getXMLParam("name"));
            }
            set { 
                setXMLParam("name",validateName( value)); 
                _nameNotChanged = (_name==value);
            }
        }


        /// <summary>
        /// Return nodes type.
        /// </summary>
        public virtual string contentType
        {
            get { return getXMLParam("type", "any"); }
            set { setXMLParam("type", value); }
        }

        /// <summary>
        /// Name of parent type of this data type. Default type is "any"
        /// </summary>
        public virtual string parentTypeName
        {
            get { return getXMLParam("parentType", "any"); }
            set { setXMLParam("parentType", value); }
        }

        /// <summary>
        /// 
        /// </summary>
        public string id
        {
            get { return getXMLParam("id", ""); }
            set { setXMLParam("id", value); }
        }


        //---------------------------
        // module settings
        //---------------------------

        /// <summary>
        /// 
        /// </summary>
        public virtual string module
        {
            get { return getXMLParam("module"); }
            set { setXMLParam("module", value); }
        }



        /// <summary>
        /// Settings xml element of this node. Used for module generator settings.
        /// </summary>
        protected XmlElement _settingsElement = null;

        /// <summary>
        /// Get/set settings xml element
        /// </summary>
        public virtual XmlElement settingsElement 
        {
            get 
            {
                if (_settingsElement == null)
                    _settingsElement = getSettingElement();
                return _settingsElement;
            }
            protected set {_settingsElement = value;}
        }

        /// <summary>
        /// name of settings xml element
        /// </summary>
        protected string settingsElementName = "setting";
        
        /// <summary>
        /// Get or create settings element
        /// </summary>
        /// <returns>xml settings element</returns>
        protected XmlElement getSettingElement()
        {
            if (_settingsElement == null)
            {
                _settingsElement = XMLUtils.getSubElement(xmlElem, settingsElementName);
                if (_settingsElement == null)
                {
                    _settingsElement = xmlElem.OwnerDocument.CreateElement(settingsElementName);
                    XmlElement refElem = XMLUtils.getFirstSubElement(xmlElem);
                    if (refElem==null)
                        xmlElem.AppendChild(_settingsElement);
                    else
                        xmlElem.InsertBefore(_settingsElement, refElem);
                }
            }
            return _settingsElement;
        }

        /// <summary>
        /// Get setting of name...
        /// </summary>
        /// <param name="name">setting name</param>
        /// <returns>value of setting</returns>
        public virtual string getSetting(string name)
        {
            XmlElement settElem = this.getSettingElement();
            if (!settElem.HasAttribute(name))
            {
                return null;
            }
            return settElem.GetAttribute(name);
        }

        /// <summary>
        /// Set value into setting of name "name"
        /// </summary>
        /// <param name="name">name of setting</param>
        /// <param name="value">value</param>
        public virtual void setSetting(string name,string value)
        {
            XmlElement settElem = this.getSettingElement();
            if (settElem == null)
            {
                logger.addErrorLog(this.name, "Set settings "+name+" failed ("+settingsElementName+" is null).");
                return;
            }

            settElem.SetAttribute(name, value);
        }

        //-----------------------------------------------
        // generating properties
        //-----------------------------------------------


        /// <summary>
        /// How many elements of this type should be generated (under parent element)
        /// </summary>
        public decimal generateLeft = 0;

        /// <summary>
        /// 
        /// </summary>
        //public decimal generatedChilds = 0;
       

        protected int _depth = -1;
        /// <summary>
        /// 
        /// </summary>
        public virtual int depth
        {
            get {
                if (_depth < 0 && parentNode != null)
                { 
                    this._depth = parentNode.depth+1;
                }
                return _depth;
            }
            set {  this._depth = value;}
        }

        internal XModuleInstance modInst = null;

        /// <summary>
        /// 
        /// </summary>
        public virtual bool initModule()
        {
            return true;
        }

        
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public virtual string getGeneratedString()
        {
            return null;
        }


        /// <summary>
        /// If this node is used as data type (or ihnerited from),
        /// show this node as root node.
        /// </summary>
        public bool isRootType = false;
    }
}
